package fr.inria.arles.temperatureManagement.processingNode;

import java.util.Properties;
import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import static fr.inria.arles.temperatureManagement.Utility.*;
import static fr.inria.arles.temperatureManagement.Constants.*;
import static fr.inria.arles.temperatureManagement.processingNode.SensorConstants.*;

/**
 * The server running at the processing node.
 *
 * @author ajay chhatwal
 */
public class ProcessingNodeServer {

    /*
     * Permissible error in temperature.
     * If difference between actual and reference temperatures is more than this
     * then actuators are invoked to change the temperature.
     */
    private static final double PERMISSIBLE_ERROR = 1.0;
    /**
     * The reference temp.
     */
    private double referenceTemp = DEFAULT_TEMP_SETTING;
    /**
     * The Partition for which Processing Node is responsible.
     */
    private String myPartition;
    /**
     * The actual temp.
     */
    private double temp;

    private void actuate(short value) {
        RadiogramConnection rCon = null;
        Datagram dg = null;

        try {
            // Open up a broadcast connection to the host port
            // where the actuator is listening
            rCon = (RadiogramConnection) Connector.open("radiogram://broadcast:"
                    + ACTUATOR_HOST_PORT);
            dg = rCon.newDatagram(10);

            // Package the time and sensor reading into a radio datagram and send it.
            dg.reset();
            dg.writeShort(value);
            rCon.send(dg);

            System.out.println("Directing actuator to : " + value);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * The server responsible for handling the communication with the sensors and
     * actuator.
     *
     * @author ajay chhatwal
     */
    private class SensingServer implements Runnable {

        @SuppressWarnings("static-access")
        public void run() {
            RadiogramConnection rCon;
            Datagram dg;

            try {
                // Open up a server-side broadcast radiogram connection
                // to listen for sensor readings being sent by different SPOTs
                rCon = (RadiogramConnection) Connector.open("radiogram://:"
                        + SENSOR_HOST_PORT);
                dg = rCon.newDatagram(rCon.getMaximumLength());
                double[] temps = new double[READING_HISTORY_LENGTH];
                // Main data collection loop

                for (int ctr = 0;; ctr = (ctr + 1) % READING_HISTORY_LENGTH) {


                    // Read sensor sample received over the radio
                    rCon.receive(dg);
                    temps[ctr] = dg.readDouble();
                    double sum = 0;
                    for (double x : temps) {
                        sum += x;
                    }
                    temp = sum / READING_HISTORY_LENGTH;
                    System.out.println("Average Temp:" + temp);

                    double diff = referenceTemp - temp;
                    if (Math.abs(diff) < PERMISSIBLE_ERROR) {
                        actuate(SWITCH_OFF);
                    } else if (diff > 0) {
                        actuate(RAISE);
                    } else {
                        actuate(LOWER);
                    }

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * The server responsible for handling the communication with the 
     * controlling devices (Phone,RFID reader,Laptop).
     *
     * @author ajay chhatwal
     */
    private class ControllingServer implements Runnable {

        public void run() {
            try {
                ServerSocket tempUpdator = new ServerSocket(PROCESSING_NODE_PORT);
                Socket s;
                while (true) {
                    s = tempUpdator.accept();
                    Scanner in = new Scanner(s.getInputStream());
                    String req = in.nextLine();
                    System.out.println("Request: " + req);
                    String[] request = decode(req);
                    String res = "false";
                    if (request.length > 0) {
                        if (request[0].equals("UpdateTemp")
                                && request.length == 3) {

                            if (myPartition.equals(request[1])) {
                                referenceTemp = Double.parseDouble(request[2]);
                                System.out.println("Reference Temp Updated to : "
                                        + referenceTemp);
                                res = "true";
                            } else {
                                System.out.println("Partition does not match");
                            }
                        } else if (request[0].equals("GetTemp") && request.length == 2) {
                            if (myPartition.equals(request[1])) {
                                res = encode("true", temp + "");
                                System.out.println("Actual Temp Sent : "
                                        + temp);
                            } else {
                                System.out.println("Partition does not match");
                            }
                        }
                    }
                    PrintWriter out = new PrintWriter(s.getOutputStream(), true);
                    out.println(res);
                    System.out.println("Response: " + res);
                    s.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Method that runs the two servers.
     * @param myPartion The Partition for which Processing Node is responsible.
     */
    public void server(String myPartion) {
        this.myPartition = myPartion;
        new Thread(new SensingServer()).start();
        new Thread(new ControllingServer()).start();
    }

    /**
     * The main method.
     * @param args Command line arguments
     */
    public static void main(String args[]) {
        Properties properties = new Properties();
		loadProperties(properties, PROP_FILE);
        for (int i = 0; i < args.length - 1; i+=2) {
            boolean valid =
                    processArg(properties, args[i], "PROCESSING_NODE_PORT", args[i + 1])
                    && processArg(properties,args[i], "SENSOR_HOST_PORT", args[i + 1])
                    && processArg(properties,args[i], "ACTUATOR_HOST_PORT", args[i + 1])
                    && processArg(properties,args[i], "DEFAULT_TEMP_SETTING", args[i + 1]);
            if (!valid) {
                System.out.println("Invalid argument :" + args[i]);
            }
        }

  
       PROCESSING_NODE_PORT = Integer.parseInt(properties.getProperty(
                "PROCESSING_NODE_PORT", PROCESSING_NODE_PORT + ""));
        SENSOR_HOST_PORT = Integer.parseInt(properties.getProperty(
                "SENSOR_HOST_PORT", SENSOR_HOST_PORT + ""));
        ACTUATOR_HOST_PORT = Integer.parseInt(properties.getProperty(
                "ACTUATOR_HOST_PORT", ACTUATOR_HOST_PORT + ""));
        DEFAULT_TEMP_SETTING = Double.parseDouble(properties.getProperty(
                "DEFAULT_TEMP_SETTING", DEFAULT_TEMP_SETTING + ""));


        new ProcessingNodeServer().server("room 27");
    }
}

